package com.kot.matchingsystem.service.impl.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

@Component//交给bean管理
public class MatchingPool extends Thread {//多线程
    //多线程公用这个玩家列表变量，会有读写冲突，所以要加锁(匹配线程和传参加入玩家的线程)
    private static List<Player> players = new ArrayList<>();//存所有匹配中的玩家，不用线程安全的list，因为下面可以自己加锁，变成安全的
    private final ReentrantLock lock = new ReentrantLock();//定义一个锁
    private static RestTemplate restTemplate;//用于向另一个springboot项目通信
    private final static String startGameUrl = "http://127.0.0.1:3000/pk/start/game/";//另一个项目中一个controller的url


    @Autowired//websocket中只能在setter上自动注入
    public void setRestTemplate(RestTemplate restTemplate) {
        MatchingPool.restTemplate = restTemplate;
    }

    public void addPlayer(Integer userId, Integer rating) {//添加一名玩家（加入匹配系统）
        lock.lock();//players读写冲突，加锁
        try {
            players.add(new Player(userId, rating,0));
        } finally {
            lock.unlock();
        }
    }

    public void removePlayer(Integer userId) {//从匹配系统中删除玩家
        lock.lock();
        try {
            List<Player> newPlayers = new ArrayList<>();//删掉
            for (Player player: players) {
                if (!player.getUserId().equals(userId)) {
                    newPlayers.add(player);
                }
            }
            players = newPlayers;
        } finally {
            lock.unlock();
        }
    }

    private void increaseWaitingTime() {  // 将所有当前玩家的等待时间加1
        for (Player player: players) {
            player.setWaitingTime(player.getWaitingTime() + 1);
        }
    }

    private boolean checkMatched(Player a, Player b) {  // 判断两名玩家是否匹配
        int ratingDelta = Math.abs(a.getRating() - b.getRating());//记录两名玩家的分差
        int waitingTime = Math.min(a.getWaitingTime(), b.getWaitingTime());//你情我愿的策略：取等待时间的最小值
        return ratingDelta <= waitingTime * 10;//
    }

    private void sendResult(Player a, Player b) {  // 返回匹配结果
//        System.out.println("send result: " + a + " " + b);
        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();//必须是一键多值的哈希表
        data.add("a_id", a.getUserId().toString());
        data.add("b_id", b.getUserId().toString());
        restTemplate.postForObject(startGameUrl, data, String.class);//用于向另一个springboot项目backend通信
    }

    private void matchPlayers() {  // 尝试匹配所有玩家
        //System.out.println("match players: " + players.toString());
        boolean[] used = new boolean[players.size()];//记录已经匹配好了的玩家
        for (int i = 0; i < players.size(); i ++ ) {//(每一次尝试匹配，都可能匹配多对玩家，所以used[]数组在这里定义是有必要的)
            //从前往后选择待匹配的玩家（因为新玩家都是加在players的后面，前面都是匹配时间已经很长，要优先匹配）
            if (used[i]) continue;//匹配好的，跳过
            for (int j = i + 1; j < players.size(); j ++ ) {//匹配第i名玩家，从他后面玩家找匹配
                if (used[j]) continue;
                Player a = players.get(i), b = players.get(j);
                if (checkMatched(a, b)) {//匹配成功
                    used[i] = used[j] = true;//记录匹配好了，不再接受匹配
                    sendResult(a, b);//发送ab匹配结果
                    break;
                }
            }
        }

        List<Player> newPlayers = new ArrayList<>();
        for (int i = 0; i < players.size(); i ++ ) {//把所有匹配好的玩家从匹配列表players中删除
            if (!used[i]) {
                newPlayers.add(players.get(i));
            }
        }
        players = newPlayers;
    }

    @Override
    public void run() {//多线程入口
        while (true) {//匹配死循环
            try {
                Thread.sleep(1000);//每隔一秒，匹配一次
                lock.lock();//对玩家列表有读写操作，所以要加锁
                try {
                    increaseWaitingTime();
                    matchPlayers();
                } finally {
                    lock.unlock();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}

